package com.evolved.automata.experimental.speech;
import java.util.*;
import java.io.*;
import java.util.regex.*;
import java.util.concurrent.*;
import java.lang.reflect.*;


public class ParametricReflectStateMachine {

	private String ConfigFileFullName;
	
	private class ActionStateList
	{
		public List<Method> Actions;
		public String NextState;
		public ActionStateList(String nextState)
		{
			NextState=nextState;
			Actions=new LinkedList<Method>();
		}
		
		public void AddNextMethod(Method a)
		{
			Actions.add(a);
		}
	}
	
	
	private HashMap<String,ActionStateList> StateEventActionMap;
	private HashMap<String,List<String>> StateNameGroups;
	private HashMap<String,List<String>> EventNameGroups;
	private HashMap<String,StateActionPerformer> ActionNames;
	
	private String CurrentStateName;
	private String PreviousStateName;
	private String CurrentEventName;
	
	private final int STATENAME_DESIGNATOR=0;
	private final int EVENTNAME_DESIGNATOR=1;
	private final String STATE_EVENT_SEPARATOR = ":";
	private final String UNKNOWN_NEXT_STATE_DESIGNATOR="*";
	private final String TIMEOUT_EVENT_NAME=".";
	private String EXIT_STATE_NAME="FINAL";
	private Pattern StateGroupPattern;
	private Pattern EventGroupPattern;
	private Pattern StateTransitionsPattern;
	private String StateGroupPatternString="\\s*\\[\\s*(State|StateName)\\s+Groups\\s*\\]";
	private String EventGroupPatternString="\\s*\\[\\s*(EventName|Event)\\s+Groups\\s*\\]";
	private String StateTransitionsPatternString="\\s*\\[\\s*State\\s*Transitions\\s*\\]";
	private String commentPatternString = "^\\-\\-.*";
	private boolean UseEventTimeoutsP=false;
	private int EventTimeoutMilli=0;
	private String UnhandledEventResumeState="";
	private String DefaultUnhandledEventName=null;
	private Class StateImplementationClass; 
	private Object StateImplementation;
	private BlockingDeque<String> EventMessageQueue;
	private static final String SelfTransitionAlias="->"; 
	private BlockingDeque<String> SynchQueue=null;
	
	
	public ParametricReflectStateMachine(String configFileFullname, Object stateImp, Class implementationClass, String finalStateName, String initialState, String unhandledEventState,int eventTimeoutMilli)
	{
		ConfigFileFullName=configFileFullname;
		StateEventActionMap = new HashMap<String, ActionStateList>();
		StateImplementationClass = implementationClass;
		EventNameGroups = new HashMap<String, List<String>>();
		StateNameGroups = new HashMap<String,List<String>>();
		StateGroupPattern = Pattern.compile(StateGroupPatternString);
		EventGroupPattern = Pattern.compile(EventGroupPatternString);
		StateTransitionsPattern=Pattern.compile(StateTransitionsPatternString);
		EventMessageQueue = new LinkedBlockingDeque<String>();
		UseEventTimeoutsP=eventTimeoutMilli>0;
		EventTimeoutMilli=eventTimeoutMilli;
		UnhandledEventResumeState=unhandledEventState;
		CurrentStateName=PreviousStateName=initialState;
		EXIT_STATE_NAME=finalStateName;
		CreateStateMap();
		StateImplementation=stateImp;
		
	}
	
	public ParametricReflectStateMachine(String configFileFullname, Object stateImp, Class implementationClass, String finalStateName, String initialState,  String unhandledEventState,BlockingDeque<String> eventMessageQueue,int eventTimeoutMilli)
	{
		ConfigFileFullName=configFileFullname;
		StateEventActionMap = new HashMap<String, ActionStateList>();
		StateImplementationClass = implementationClass;
		EventNameGroups = new HashMap<String, List<String>>();
		StateNameGroups = new HashMap<String,List<String>>();
		StateGroupPattern = Pattern.compile(StateGroupPatternString);
		EventGroupPattern = Pattern.compile(EventGroupPatternString);
		StateTransitionsPattern=Pattern.compile(StateTransitionsPatternString);
		EventMessageQueue = eventMessageQueue;
		UseEventTimeoutsP=eventTimeoutMilli>0;
		EventTimeoutMilli=eventTimeoutMilli;
		UnhandledEventResumeState=unhandledEventState;
		CurrentStateName=PreviousStateName=initialState;
		EXIT_STATE_NAME=finalStateName;
		CreateStateMap();
		StateImplementation=stateImp;
	}
	public ParametricReflectStateMachine(String configFileFullname, Object stateImp, Class implementationClass, String finalStateName, String initialState,  String unhandledEventState)
	{
		ConfigFileFullName=configFileFullname;
		StateEventActionMap = new HashMap<String, ActionStateList>();
		StateImplementationClass = implementationClass;
		EventNameGroups = new HashMap<String, List<String>>();
		StateNameGroups = new HashMap<String,List<String>>();
		StateGroupPattern = Pattern.compile(StateGroupPatternString);
		EventGroupPattern = Pattern.compile(EventGroupPatternString);
		StateTransitionsPattern=Pattern.compile(StateTransitionsPatternString);
		EventMessageQueue = new LinkedBlockingDeque<String>();
		UseEventTimeoutsP=false;
		UnhandledEventResumeState=unhandledEventState;
		CurrentStateName=PreviousStateName=initialState;
		EXIT_STATE_NAME=finalStateName;
		CreateStateMap();
		StateImplementation=stateImp;
	}
	
	public ParametricReflectStateMachine(String configFileFullname, Object stateImp, Class implementationClass, String finalStateName, String initialState, String unhandledEventState,BlockingDeque<String> eventMessageQueue)
	{
		ConfigFileFullName=configFileFullname;
		StateEventActionMap = new HashMap<String, ActionStateList>();
		EventNameGroups = new HashMap<String, List<String>>();
		StateNameGroups = new HashMap<String,List<String>>();
		StateGroupPattern = Pattern.compile(StateGroupPatternString);
		EventGroupPattern = Pattern.compile(EventGroupPatternString);
		StateTransitionsPattern=Pattern.compile(StateTransitionsPatternString);
		EventMessageQueue = eventMessageQueue;
		UseEventTimeoutsP=false;
		UnhandledEventResumeState=unhandledEventState;
		CurrentStateName=PreviousStateName=initialState;
		EXIT_STATE_NAME=finalStateName;
		CreateStateMap();
		StateImplementationClass = implementationClass;
		StateImplementation=stateImp;
	}
	
	public BlockingDeque<String> GetEventMessageQueue()
	{
		return  EventMessageQueue;
	}
	
	
	public void SetResumeStateForUnHandledEvent(String resumeState)
	{
		UnhandledEventResumeState=resumeState;
	}
	
	public void SetDefaultUnhandledEventName(String unhandledEventName)
	{
		DefaultUnhandledEventName=unhandledEventName;
	}
	
	public void ResetUnhandledEventName()
	{
		DefaultUnhandledEventName=null;
	}
	
	public String ProcessStateMachine(String initialState)
	{
		CurrentStateName=initialState;
		return ProcessStateMachine();
	}
	
	public String ProcessStateMachine()
	{
		ActionStateList actionList;
		try
		{
			Runnable timeoutEvent=null;
			Thread timeoutThread=null;
			if (UseEventTimeoutsP)
			{
				timeoutEvent = new Runnable()
				{
					public void run()
					{
						try
						{
							Thread.sleep(EventTimeoutMilli);
							EventMessageQueue.put(TIMEOUT_EVENT_NAME);
						}
						catch (InterruptedException i)
						{
							
						}
					}
				};
				timeoutThread = new Thread(timeoutEvent);
				timeoutThread.start();
			}
			CurrentEventName = EventMessageQueue.take();
			if (UseEventTimeoutsP&&timeoutThread.isAlive()&&!CurrentStateName.equals(TIMEOUT_EVENT_NAME))
				timeoutThread.interrupt();
			
			System.out.printf("Current event/state is: %1$s - %2$s\n", CurrentEventName,CurrentStateName);
			actionList=GetNextActionList();
			while ((!CurrentStateName.equals(EXIT_STATE_NAME)))
			{
				// handling previous event
				PreviousStateName=CurrentStateName;
				if (actionList!=null)
				{
					
					CurrentStateName=ExecuteActionList(actionList);
				}
				else
				{	
					
					//EventMessageQueue.clear();
					if (DefaultUnhandledEventName==null)
					{
						CurrentStateName=UnhandledEventResumeState;
						EventMessageQueue.put(CurrentEventName);
					}
					else
					{
						CurrentEventName=DefaultUnhandledEventName;
						actionList=GetNextActionList();
						if (actionList==null)
							throw new Exception("Unhandled event Exception!!");
						else
							CurrentStateName=ExecuteActionList(actionList);
					}
				}
				if (CurrentStateName.equals(PreviousStateName)&&CurrentStateName.equals(UnhandledEventResumeState))
					throw new Exception("Infinite State Loop Exception!!");
				if (CurrentStateName.equals(EXIT_STATE_NAME))
					break;
				// Waiting on next event
				if (UseEventTimeoutsP)
				{
					timeoutThread = new Thread(timeoutEvent);
					timeoutThread.start();
				}
				if (SynchQueue!=null)
				{
//					if (SynchQueue.size()>0)
//						throw new Exception("Synch exception");
					System.out.println("Putting synch signal: ");
					SynchQueue.put("cont");
					
				}
				CurrentEventName = EventMessageQueue.take();
				System.out.printf("Current event/state is: %1$s - %2$s\n", CurrentEventName,CurrentStateName);
				if (UseEventTimeoutsP&&timeoutThread.isAlive()&&!CurrentStateName.equals(TIMEOUT_EVENT_NAME))
					timeoutThread.interrupt();
				actionList=GetNextActionList();
				
			}
			if (SynchQueue!=null)
				SynchQueue.put("cont");
		}
		catch (Exception e)
		{
			java.io.StringWriter traceText = new StringWriter();
			java.io.PrintWriter pWriter = new PrintWriter(traceText,true);
			e.printStackTrace(pWriter);
			pWriter.close();
			throw new RuntimeException(traceText.toString());
		}
		
		return CurrentStateName;
	}
	
	public BlockingDeque<String> GetSynchQueue()
	{
		SynchQueue = new LinkedBlockingDeque<String>();
		return SynchQueue;
	}
	
	private void CreateStateMap()
	{
		BufferedReader bfReader=null;
		boolean groupsDefined=false,mappingStateGroupsP=false,mappingEventGroupsP=false,mappingStateTransitionsP=false;
		
		try
		{
			bfReader = new BufferedReader(new FileReader(ConfigFileFullName));
			String lineInput="";
			
			while ((lineInput=bfReader.readLine())!=null)
			{
				if ((Pattern.matches(commentPatternString, lineInput))||(lineInput.trim().equals("")))
					continue;
				if (!mappingStateTransitionsP&&BeginningOfStateGroup(lineInput))
				{
					groupsDefined=true;
					mappingStateGroupsP=true;
					continue;
				}
				if (!mappingStateTransitionsP&&BeginningOfEventGroup(lineInput))
				{
					groupsDefined=true;
					mappingEventGroupsP=true;
					continue;
				}
				if (!mappingStateTransitionsP&&BeginningOfStateTransitions(lineInput))
				{
					groupsDefined=true;
					mappingStateTransitionsP=true;
					mappingEventGroupsP=mappingStateGroupsP=false;
					continue;
				}
				
				if (mappingStateGroupsP)
					MapStateGroupDefinition(lineInput);
				else if (mappingEventGroupsP)
					MapEventGroupDefinition(lineInput);
				else if (!groupsDefined||mappingStateTransitionsP)
				{	
					MapStateEventsToActionLists(lineInput);
					mappingStateTransitionsP=true;
				}
				
			}
			
		}
		catch (Exception e)
		{
			java.io.StringWriter traceText = new StringWriter();
			java.io.PrintWriter pWriter = new PrintWriter(traceText,true);
			e.printStackTrace(pWriter);
			pWriter.close();
			throw new RuntimeException(traceText.toString());
		}
		finally
		{
			if (bfReader!=null)
				try {
					bfReader.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		}
	}
	
	private boolean BeginningOfStateGroup(String lineInput)
	{
		Matcher m = StateGroupPattern.matcher(lineInput);
		return m.matches();
	}
	
	private boolean BeginningOfEventGroup(String lineInput)
	{
		Matcher m = EventGroupPattern.matcher(lineInput);
		return m.matches();
	}
	
	private boolean BeginningOfStateTransitions(String lineInput)
	{
		Matcher m = StateTransitionsPattern.matcher(lineInput);
		return m.matches();
	}
	
	private void MapStateGroupDefinition(String lineInput)
	{
		String[] components = lineInput.split(",");
		String groupName=components[0].trim();
		String statename;
		for (int i=1;i<components.length;i++)
		{
			statename=components[i].trim();
			if (!StateNameGroups.containsKey(statename))
				StateNameGroups.put(statename, new LinkedList<String>());
			StateNameGroups.get(statename).add(groupName);
		}
	}
	
	private void MapEventGroupDefinition(String lineInput)
	{
		String[] components = lineInput.split(",");
		String groupName=components[0].trim();
		String eventname;
		for (int i=1;i<components.length;i++)
		{
			eventname=components[i].trim();
			if (!EventNameGroups.containsKey(eventname))
				EventNameGroups.put(eventname, new LinkedList<String>());
			EventNameGroups.get(eventname).add(groupName);
		}
	}
	
	private void MapStateEventsToActionLists(String stateEventActionLine)
	{
		String[] components = stateEventActionLine.split(",");
		String stateDesignator = components[STATENAME_DESIGNATOR].trim();
		String eventDesignator = components[EVENTNAME_DESIGNATOR].trim();
		String state_event_key=CreateStateEventKey(stateDesignator,eventDesignator);
		String nextStateName=components[components.length-1].trim();
		
		ActionStateList asList; 
		if (nextStateName.equals(UNKNOWN_NEXT_STATE_DESIGNATOR))
			asList= new ActionStateList(null);
		else
			asList= new ActionStateList(nextStateName.trim());
		Method impMethod;
		Class[] impParameters = {String.class,Object.class};
		for (int i=2;i<components.length-1;i++)
		{
			try
			{
				impMethod = StateImplementationClass.getMethod(components[i].trim(), impParameters);
				asList.AddNextMethod(impMethod);
			}
			catch (Exception e)
			{
				java.io.StringWriter traceText = new StringWriter();
				java.io.PrintWriter pWriter = new PrintWriter(traceText,true);
				e.printStackTrace(pWriter);
				pWriter.close();
				throw new RuntimeException(traceText.toString());
			}
			
		}
		StateEventActionMap.put(state_event_key, asList);
		
	}
	
	
	
	private String CreateStateEventKey(String statename, String eventname)
	{
		return String.format("%1$s%2$s%3$s",statename,STATE_EVENT_SEPARATOR,eventname);
	}
	

	private String ExecuteActionList(ActionStateList actionList)
	{
		String outputState;
		if ((actionList.NextState!=null)&&(actionList.NextState.equals(SelfTransitionAlias)))
			outputState=CurrentStateName;
		else
			outputState = actionList.NextState;
		Object output=null;
		
		List<Method> actions=actionList.Actions;
		int numActions = actions.size();
		Method action;
		try
		{
			for (int i=0;i<numActions;i++)
			{
				action=actions.get(i);
				if (i==0)
				{
					output=action.invoke(StateImplementation, CurrentStateName,CurrentEventName);
					
				}
				else
					output=action.invoke(StateImplementation, CurrentStateName,output);
			}
		}
		catch (Exception e)
		{
			java.io.StringWriter traceText = new StringWriter();
			java.io.PrintWriter pWriter = new PrintWriter(traceText,true);
			e.printStackTrace(pWriter);
			pWriter.close();
			throw new RuntimeException(traceText.toString());
		}
		if (outputState==null)
			return (String)output;
		else
			return outputState;
	}
	
	private ActionStateList GetNextActionList()
	{
		List<String> stateGroupMembership = StateNameGroups.get(CurrentStateName);
		List<String> eventGroupMembership = EventNameGroups.get(CurrentEventName);
		int numSGroups = (stateGroupMembership!=null)?stateGroupMembership.size():0;
		int numEGroups = (eventGroupMembership!=null)?eventGroupMembership.size():0;
		
		String stateDesignator;
		String eventDesignator;
		String combinedKey;
		ActionStateList a=null;
		for (int i=0;i<=numSGroups;i++)
		{
			if (i==0)
				stateDesignator=CurrentStateName;
			else
				stateDesignator=stateGroupMembership.get(i-1);
			for (int j=0;j<=numEGroups;j++)
			{
				if (j==0)
					eventDesignator=CurrentEventName;
				else
					eventDesignator=eventGroupMembership.get(j-1);
				combinedKey=CreateStateEventKey(stateDesignator,eventDesignator);
				
				if ((a=StateEventActionMap.get(combinedKey))!=null)
					return a;
			}
		}
		return null;
	}
	
	
}
